package com.tsfyun.common.base.util;

import cn.hutool.core.codec.Base64;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.Cipher;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;


/**
 * @Description: RSA加密工具类
 * @CreateDate: Created in 2021/3/11 17:30
 */
@Slf4j
public class RSAUtils {

	// MAX_DECRYPT_BLOCK应等于密钥长度/8（1byte=8bit），所以当密钥位数为2048时，最大解密长度应为256.
	// 128 对应 1024，256对应2048
	private static final int KEYSIZE = 2048;

	// RSA最大加密明文大小
	private static final int MAX_ENCRYPT_BLOCK = 117;

	// RSA最大解密密文大小
	private static final int MAX_DECRYPT_BLOCK = 256;

	// 不仅可以使用DSA算法，同样也可以使用RSA算法做数字签名
	private static final String KEY_ALGORITHM = "RSA";

	private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";

	public static final String PUBLIC_KEY = "PublicKey";

	public static final String PRIVATE_KEY = "PrivateKey";

	/**
	 *
	 * 生成密钥
	 * @return 密钥对象
	 * @throws Exception
	 *
	 */

	public static Map<String, Key> initKey( ) throws Exception {
		KeyPairGenerator keygen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
		SecureRandom secureRandom = new SecureRandom();
		keygen.initialize(KEYSIZE, secureRandom);
		KeyPair keys = keygen.genKeyPair();
		PrivateKey privateKey = keys.getPrivate();
		PublicKey publicKey = keys.getPublic();
		Map<String, Key> map = new HashMap<>(2);
		map.put(PUBLIC_KEY, publicKey);
		map.put(PRIVATE_KEY, privateKey);
		return map;
	}


	/**
	 *
	 * 取得私钥
	 * @param keyMap
	 * @return
	 * @throws Exception
	 *
	 */
	public static String getPrivateKey(Map<String, Key> keyMap) throws Exception {
		Key key = keyMap.get(PRIVATE_KEY);
		return encryptBASE64(key.getEncoded()); // base64加密私钥
	}

	/**
	 *
	 * 取得公钥
	 *
	 * @param keyMap
	 *
	 * @return
	 * @throws Exception
	 *
	 */
	public static String getPublicKey(Map<String, Key> keyMap) throws Exception {
		Key key = keyMap.get(PUBLIC_KEY);
		return encryptBASE64(key.getEncoded()); // base64加密公钥
	}

	/**
	 *
	 * 用私钥对信息进行数字签名
	 * @param data       加密数据
	 * @param privateKey 私钥-base64加密的
	 * @return
	 *
	 * @throws Exception
	 *
	 */
	public static String signByPrivateKey(byte[] data, String privateKey) throws Exception {
		byte[] keyBytes = decryptBASE64(privateKey);
		PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
		KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM);
		PrivateKey priKey = factory.generatePrivate(keySpec);// 生成私钥
		//用私钥对信息进行数字签名
		Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
		signature.initSign(priKey);
		signature.update(data);
		return encryptBASE64(signature.sign());

	}

	/**
	 *
	 * BASE64Encoder 加密
	 * @param data 要加密的数据
	 * @return 加密后的字符串
	 */
	private static String encryptBASE64(byte[] data) {
		return Base64.encode(data);
	}

	private static byte[] decryptBASE64(String data) {
		return Base64.decode(data);
	}

	public static boolean verifyByPublicKey(byte[] data, String publicKey, String sign) throws Exception {
		byte[] keyBytes = decryptBASE64(publicKey);
		X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		PublicKey pubKey = keyFactory.generatePublic(keySpec);
		Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
		signature.initVerify(pubKey);
		signature.update(data);
		//验证签名
		return signature.verify(decryptBASE64(sign));
	}

	/**
	 * RSA公钥加密
	 *
	 * @param str       加密字符串
	 * @param publicKey 公钥
	 * @return 密文
	 * @throws Exception                    加密过程中的异常信息
	 */
	public static String encryptByPublicKey(String str, String publicKey) throws Exception {
		//base64编码的公钥
		byte[] keyBytes = decryptBASE64(publicKey);
		RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance(KEY_ALGORITHM)
				.generatePublic(new X509EncodedKeySpec(keyBytes));
		//RSA加密
		Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
		cipher.init(Cipher.ENCRYPT_MODE, pubKey);
		byte[] data = str.getBytes("UTF-8");
		//加密时超过117字节就报错。为此采用分段加密的办法来加密
		byte[] enBytes = null;
		for (int i = 0; i < data.length; i += MAX_ENCRYPT_BLOCK) {
			// 注意要使用2的倍数，否则会出现加密后的内容再解密时为乱码
			byte[] doFinal = cipher.doFinal(ArrayUtil.subarray(data, i, i + MAX_ENCRYPT_BLOCK));
			enBytes = ArrayUtil.addAll(enBytes, doFinal);
		}
		String outStr = encryptBASE64(enBytes);
		return outStr;
	}

	/**
	 * RSA私钥加密
	 *
	 * @param str       加密字符串
	 * @param privateKey 公钥
	 * @return 密文
	 * @throws Exception                    加密过程中的异常信息
	 */
	public static String encryptByPrivateKey(String str, String privateKey) throws Exception {
		// base64编码的公钥
		byte[] keyBytes = decryptBASE64(privateKey);
		RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance(KEY_ALGORITHM)
				.generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
		// RSA加密
		Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
		cipher.init(Cipher.ENCRYPT_MODE, priKey);

		byte[] data = str.getBytes("UTF-8");
		// 加密时超过117字节就报错。为此采用分段加密的办法来加密
		byte[] enBytes = null;
		for (int i = 0; i < data.length; i += MAX_ENCRYPT_BLOCK) {
			// 注意要使用2的倍数，否则会出现加密后的内容再解密时为乱码
			byte[] doFinal = cipher.doFinal(ArrayUtil.subarray(data, i, i + MAX_ENCRYPT_BLOCK));
			enBytes = ArrayUtil.addAll(enBytes, doFinal);
		}
		String outStr = encryptBASE64(enBytes);
		return outStr;
	}

	/**
	 * RSA私钥解密
	 *
	 * @param encryStr   加密字符串
	 * @param privateKey 私钥
	 * @return 明文
	 * @throws Exception                 解密过程中的异常信息
	 */
	public static String decryptByPrivateKey(String encryStr, String privateKey) throws Exception{
		// base64编码的私钥
		byte[] decoded = decryptBASE64(privateKey);
		RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance(KEY_ALGORITHM)
				.generatePrivate(new PKCS8EncodedKeySpec(decoded));
		// RSA解密
		Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
		cipher.init(Cipher.DECRYPT_MODE, priKey);

		// 64位解码加密后的字符串
		byte[] data = decryptBASE64(encryStr);
		// 解密时超过128字节报错。为此采用分段解密的办法来解密
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < data.length; i += MAX_DECRYPT_BLOCK) {
			byte[] doFinal = cipher.doFinal(ArrayUtil.subarray(data, i, i + MAX_DECRYPT_BLOCK));
			sb.append(new String(doFinal));
		}

		return sb.toString();
	}

	/**
	 * RSA公钥解密
	 *
	 * @param encryStr   加密字符串
	 * @param publicKey  公钥
	 * @return 明文
	 * @throws Exception                 解密过程中的异常信息
	 */
	public static String decryptByPublicKey(String encryStr, String publicKey) throws Exception {
		// base64编码的私钥
		byte[] decoded = decryptBASE64(publicKey);
		RSAPublicKey priKey = (RSAPublicKey) KeyFactory.getInstance(KEY_ALGORITHM)
				.generatePublic(new X509EncodedKeySpec(decoded));
		// RSA解密
		Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
		cipher.init(Cipher.DECRYPT_MODE, priKey);

		// 64位解码加密后的字符串
		byte[] data = decryptBASE64(encryStr);
		// 解密时超过128字节报错。为此采用分段解密的办法来解密
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < data.length; i += MAX_DECRYPT_BLOCK) {
			byte[] doFinal = cipher.doFinal(ArrayUtil.subarray(data, i, i + MAX_DECRYPT_BLOCK));
			sb.append(new String(doFinal));
		}

		return sb.toString();
	}

	/**
	 * main方法测试 第一种用法：公钥加密，私钥解密。---用于加解密 第二种用法：私钥签名，公钥验签。---用于签名
	 *
	 * @param args
	 * @throws Exception
	 */
	public static void main(String[] args) throws Exception {
		String ss = "张三";
		byte[] data = ss.getBytes();
		Map<String, Key> keyMap = initKey();// 构建密钥
		String publicKey  = Base64.encode(keyMap.get(PUBLIC_KEY).getEncoded());
		String privateKey =  Base64.encode(keyMap.get(PRIVATE_KEY).getEncoded());
		log.info("公钥\n {}",publicKey);
		log.info("私钥\n {}",privateKey);
		//产生签名
		String sign = signByPrivateKey(data, getPrivateKey(keyMap));
		log.info("签名sign={}", sign);
		//验证签名
		boolean verify = verifyByPublicKey(data, getPublicKey(keyMap), sign);
		log.error("经验证数据和签名匹配：{} ", verify);

		String s = "单红宇测试，哈哈";
		String signStr = encryptByPublicKey(s, getPublicKey(keyMap));
		log.info("字符串 {} 的公钥加密结果为：\n{}", s, signStr);
		String decryStr = decryptByPrivateKey(signStr, getPrivateKey(keyMap));
		log.info("私钥解密结果为：{}", decryStr);

		log.info("========================================================================================");

		String s2 = "单红宇测试222，嘿嘿";
		String encryStr2 = encryptByPrivateKey(s2, getPrivateKey(keyMap));
		log.info("字符串 {} 的私钥加密结果为：\n{}", s2, encryStr2);
		String decryStr2 = decryptByPublicKey(encryStr2, getPublicKey(keyMap));
		log.info("公钥解密结果为：{}", decryStr2);

	}


}